/**
 * Copyright © 2012, Grid Protection Alliance.  All Rights Reserved.
 *
 * Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
 * the NOTICE file distributed with this work for additional information regarding copyright ownership.
 * The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain a copy of the License at:
 *
 * http://www.opensource.org/licenses/eclipse-1.0.php
 *
 * Unless agreed to in writing, the subject software distributed under the License is distributed on an
 * "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
 * License for the specific language governing permissions and limitations.
 */
package org.gridprotectionalliance.oam.proxy.impl

import com.automatak.archivist.api.historian.{ MetaData, HistorianClient }
import org.gridprotectionalliance.oam.proxy.HistorianClientFactory
import concurrent.stm._
import collection.immutable.SortedMap
import com.automatak.archivist.api.historian.MetaData
import com.automatak.archivist.protocol.proto.Requests.{ SortOrder, ListDirection }

case class HistorianRecord(cfg: HistorianConfig, client: HistorianClient)
case class FactoryRecord(factory: HistorianClientFactory, instances: Map[String, HistorianRecord])
case class MetaDataRecord(md: MetaData, histName: String)
case class Operation[A](hist: HistorianClient, values: List[A]) {
  def add(a: A): Operation[A] = this.copy(values = a :: values)
}

class ProxyState {

  private val historianNameMap = TMap.empty[String, HistorianRecord]
  private val factoryTypeMap = TMap.empty[String, FactoryRecord]
  private val metadataMap = Ref(SortedMap.empty[String, MetaDataRecord])

  def addFactory(factory: HistorianClientFactory): Unit = atomic { implicit txn =>
    factoryTypeMap.get(factory.typ) match {
      case Some(x) =>
        throw new Exception("HistorianFactory with that type already registered: " + factory.typ)
      case None =>
        factoryTypeMap.put(factory.typ, FactoryRecord(factory, Map.empty[String, HistorianRecord]))
    }
  }

  def removeFactory(typ: String): Map[String, HistorianRecord] = atomic { implicit txn =>
    factoryTypeMap.remove(typ) match {
      case Some(x) =>
        x.instances.foreach { case (n, rec) => historianNameMap.remove(rec.cfg.name) }
        x.instances
      case None =>
        throw new Exception("No factory registered with type: " + typ)
    }
  }

  def addHistorianConfig(cfg: HistorianConfig): Option[FactoryRecord] = atomic { implicit txn =>
    historianNameMap.get(cfg.name) match {
      case Some(x) => throw new Exception("Historian with that name already exists: " + cfg.name)
      case None => factoryTypeMap.get(cfg.typ)
    }
  }

  def removeHistorian(name: String): HistorianRecord = atomic { implicit txn =>
    historianNameMap.get(name) match {
      case None => throw new Exception("Historian with that name doesn't exist: " + name)
      case Some(hr) => factoryTypeMap.get(hr.cfg.typ) match {
        case Some(fr) =>
          factoryTypeMap.update(hr.cfg.typ, fr.copy(instances = fr.instances - hr.cfg.name))
          hr
        case None =>
          throw new IllegalStateException("No factory registered for type: " + hr.cfg.typ)
      }
    }
  }

  def getMetaData(from: Option[String], until: Option[String], limit: Int, direction: ListDirection, sort: SortOrder): List[MetaDataRecord] = {
    atomic { implicit txn =>
      val submap = metadataMap.single.get.rangeImpl(from, until)
      val ordered = if (direction == ListDirection.RIGHT) SortedMap.empty[String, MetaDataRecord](submap.ordering.reverse) ++ submap else submap
      val limited = ordered.take(limit)
      val sorted = if (sort == SortOrder.REVERSE) limited.values.toList.reverse else limited.values.toList
      sorted
    }
  }

  def getHistorianForId(id: String): Option[HistorianRecord] = atomic { implicit txn =>
    metadataMap.single.get.get(id).flatMap { md => historianNameMap.get(md.histName) }
  }

  def addActiveHistorian(rec: HistorianRecord): Unit = atomic { implicit txn =>
    historianNameMap.put(rec.cfg.name, rec)
    factoryTypeMap.get(rec.cfg.typ) match {
      case Some(fr) =>
        factoryTypeMap.update(rec.cfg.typ, fr.copy(instances = fr.instances + (rec.cfg.name -> rec)))
      case None => throw new Exception("No factory with that type in map: " + rec.cfg.typ)
    }
  }

  def addMetadata(md: List[MetaData], historianName: String): Unit = atomic { implicit txn =>
    md.foreach(m => metadataMap.transform(map => map + (m.id -> MetaDataRecord(m, historianName))))
  }

  def partitionOperation[A](values: Traversable[A])(id: A => String): List[Operation[A]] = atomic { implicit txn =>
    val map = Map.empty[String, Operation[A]] // historian name to operations
    values.foldLeft(map) { (map, v) =>
      metadataMap.get.get(id(v)) match {
        case Some(mdr) => historianNameMap.get(mdr.histName) match {
          case Some(hr) =>
            map.get(mdr.histName) match {
              case Some(io) => map + (mdr.histName -> io.add(v))
              case None => map + (mdr.histName -> Operation[A](hr.client, List(v)))
            }
          case None =>
            throw new Exception("Historian not active: " + mdr.histName)
        }
        case None => throw new Exception("No metadata for id: " + id(v))
      }
    }
    map.values.toList
  }

}
